port vehicle system - #1477
Conversation
|
Warning Review limit reached
Next review available in: 35 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughРеализована абстракция транспорта (VehicleSystem) с компонентами Operator/Strap/Container/GenericKeyed, на которую переведены Mech, Buckle, Mover, DoAfter и CardboardBox. Добавлена подсистема BkmVovaMech (сервер, shared, клиент с UI рук меха). Обновлены прототипы транспорта, локализация, миграции; кнопка спонсора перенесена из главного меню в меню паузы. ChangesТранспортная система и миграция
Estimated code review effort: 5 (Critical) | ~150 minutes Клиентский UI рук пилотируемого меха
Estimated code review effort: 4 (Complex) | ~60 minutes Кнопка спонсора
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant Player
participant VehicleSystem
participant Operator as VehicleOperatorComponent
participant BkmVovaMechSystem
Player->>VehicleSystem: TrySetOperator(vehicle, user)
VehicleSystem->>Operator: установить Vehicle
VehicleSystem->>BkmVovaMechSystem: VehicleOperatorSetEvent
BkmVovaMechSystem->>BkmVovaMechSystem: EnsureActiveMechHand
VehicleSystem->>VehicleSystem: RefreshCanRun
sequenceDiagram
participant ClientBkmVovaMechSystem
participant BkmVovaMechHandsUIController
participant HandsSystem
ClientBkmVovaMechSystem->>BkmVovaMechHandsUIController: LocalPilotedMechChanged(mech)
BkmVovaMechHandsUIController->>BkmVovaMechHandsUIController: LoadMechHands
BkmVovaMechHandsUIController->>HandsSystem: MechHandClick / SetActiveHand
HandsSystem-->>BkmVovaMechHandsUIController: обновление кнопки
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (5)
Content.Shared/Vehicle/VehicleSystem.Key.cs (1)
28-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueДублирование логики между
OnGenericKeyedEntInsertedиOnGenericKeyedEntRemoved.Оба обработчика идентичны за исключением имени: проверяют
_timing.ApplyingState, ID контейнера, резолвятVehicleComponentи вызываютRefreshCanRun. Можно вынести общую логику в приватный метод.♻️ Пример рефакторинга
- private void OnGenericKeyedEntInserted(Entity<GenericKeyedVehicleComponent> ent, ref EntInsertedIntoContainerMessage args) - { - if (_timing.ApplyingState || args.Container.ID != ent.Comp.ContainerId) - return; - - if (!_vehicleQuery.TryComp(ent, out var vehicle)) - return; - - RefreshCanRun((ent.Owner, vehicle)); - } - - private void OnGenericKeyedEntRemoved(Entity<GenericKeyedVehicleComponent> ent, ref EntRemovedFromContainerMessage args) - { - if (_timing.ApplyingState || args.Container.ID != ent.Comp.ContainerId) - return; - - if (!_vehicleQuery.TryComp(ent, out var vehicle)) - return; - - RefreshCanRun((ent.Owner, vehicle)); - } + private void OnGenericKeyedEntInserted(Entity<GenericKeyedVehicleComponent> ent, ref EntInsertedIntoContainerMessage args) + { + if (args.Container.ID == ent.Comp.ContainerId) + TryRefreshKeyedCanRun(ent); + } + + private void OnGenericKeyedEntRemoved(Entity<GenericKeyedVehicleComponent> ent, ref EntRemovedFromContainerMessage args) + { + if (args.Container.ID == ent.Comp.ContainerId) + TryRefreshKeyedCanRun(ent); + } + + private void TryRefreshKeyedCanRun(Entity<GenericKeyedVehicleComponent> ent) + { + if (_timing.ApplyingState || !_vehicleQuery.TryComp(ent, out var vehicle)) + return; + + RefreshCanRun((ent.Owner, vehicle)); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Content.Shared/Vehicle/VehicleSystem.Key.cs` around lines 28 - 48, `OnGenericKeyedEntInserted` and `OnGenericKeyedEntRemoved` in `VehicleSystem.Key` duplicate the same state checks, container ID guard, vehicle lookup, and `RefreshCanRun` call; extract this shared flow into a private helper and have both handlers delegate to it. Keep the helper centered around the existing `Entity<GenericKeyedVehicleComponent>` and `RefreshCanRun` path so the behavior remains identical while removing the repeated logic.Content.Shared/Backmen/VovaMech/SharedBkmVovaMechSystem.cs (2)
154-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winДублирование логики старта DoAfter входа.
Этот блок почти идентичен
StartEntryDoAfterвContent.Server/Backmen/VovaMech/BkmVovaMechSystem.cs(строки 106-114). Стоит вынести общий метод (например,protected void StartEntryDoAfter(EntityUid uid, BkmPilotableMechComponent component, EntityUid user)) в этот shared-класс и переиспользовать его изOnDragDropи из серверных verb-обработчиков, чтобы избежать расхождения параметров DoAfterArgs (например,BreakOnMove) в будущем.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Content.Shared/Backmen/VovaMech/SharedBkmVovaMechSystem.cs` around lines 154 - 167, The DoAfter startup logic for mech entry is duplicated between OnDragDrop and StartEntryDoAfter, so move the shared setup into a common helper in BkmVovaMechSystem (for example, a protected StartEntryDoAfter method taking EntityUid uid, BkmPilotableMechComponent component, and the user/dragged entity). Have both OnDragDrop and the server verb path call that helper so the DoAfterArgs construction stays consistent, including flags like BreakOnMove and the entry event type.
17-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winСделать общие зависимости
protectedвместоprivate.
_actionBlockerи_doAfterобъявленыprivate, из-за чего серверныйBkmVovaMechSystemвынужден повторно инжектировать те же системы отдельными полями (см. Content.Server/Backmen/VovaMech/BkmVovaMechSystem.cs, строки 19-23). Это дублирование DI и потенциальный источник рассинхронизации при будущих изменениях.♻️ Предлагаемый рефакторинг
- [Dependency] private ActionBlockerSystem _actionBlocker = default!; + [Dependency] protected ActionBlockerSystem ActionBlocker = default!; [Dependency] private SharedContainerSystem _container = default!; - [Dependency] private SharedDoAfterSystem _doAfter = default!; + [Dependency] protected SharedDoAfterSystem DoAfter = default!; [Dependency] private SharedHandsSystem _hands = default!; [Dependency] protected VehicleSystem Vehicle = default!;и убрать дублирующие поля
_actionBlocker/_doAfter/_vehicleв серверном классе, заменив их наActionBlocker/DoAfter/Vehicle.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Content.Shared/Backmen/VovaMech/SharedBkmVovaMechSystem.cs` around lines 17 - 21, Make the shared dependencies in SharedBkmVovaMechSystem protected instead of private so the server-side BkmVovaMechSystem can reuse them directly rather than reinjecting the same systems. Update the dependency fields for _actionBlocker and _doAfter in SharedBkmVovaMechSystem, then remove the duplicated ActionBlocker/DoAfter/_vehicle fields from BkmVovaMechSystem and switch its usage to the inherited ActionBlocker, DoAfter, and Vehicle members.Content.Shared/Mech/EntitySystems/SharedMechSystem.cs (1)
67-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winОбработчики атаки/оружия для
VehicleOperatorComponentрасположены в mech-специфичной системе.
OnGetMeleeWeapon,OnCanAttackFromContainer,OnAttackAttemptподписаны на общийVehicleOperatorComponent, то есть будут срабатывать для ЛЮБОГО транспорта (инвалидное кресло, джаникарт и т.д.), а не только для меха. Логика внутри их защищена проверкамиTryComp<MechComponent>/_pilotableMechQuery, поэтому функционально не ломается, но с точки зрения границ модулей это создаёт неявную зависимость всех транспортных средств отSharedMechSystem. Целесообразнее вынести общую часть (AttackAttemptEvent/CanAttackFromContainerEventcancel-логику "нельзя атаковать собственный транспорт") вVehicleSystem, оставив вSharedMechSystemтолько mech-специфичныйGetMeleeWeaponEventfallback.Also applies to: 393-432
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Content.Shared/Mech/EntitySystems/SharedMechSystem.cs` around lines 67 - 69, The attack/weapon event subscriptions on VehicleOperatorComponent are too broad for SharedMechSystem and should be split by responsibility. Move the generic cancel logic for AttackAttemptEvent and CanAttackFromContainerEvent into VehicleSystem, keep only the mech-specific GetMeleeWeaponEvent fallback in SharedMechSystem, and preserve the existing TryComp<MechComponent>/_pilotableMechQuery checks while relocating the handlers OnGetMeleeWeapon, OnCanAttackFromContainer, and OnAttackAttempt to the appropriate system.Content.Server/Mech/Systems/MechSystem.cs (1)
314-314: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winУберите дублирующий
_actionBlocker.UpdateCanMoveвMechSystem.Vehicle.RefreshCanRun(uid)уже вызывает_actionBlocker.UpdateCanMove(uid)и обновляет appearance, поэтому послеInsertBattery/RemoveBatteryэтот вызов лишний. В оставшихся прямых обработчиках (OnInteractUsing,OnInsertBattery,OnRemoveBattery,OnMapInit) лучше перейти наVehicle.RefreshCanRun(uid), чтобы поведение не расходилось.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Content.Server/Mech/Systems/MechSystem.cs` at line 314, Remove the redundant direct `_actionBlocker.UpdateCanMove` path in `MechSystem`: `Vehicle.RefreshCanRun(uid)` already performs the move-state update and appearance refresh, so the extra call after `InsertBattery`/`RemoveBattery` should be dropped. Update the remaining direct handlers in `MechSystem` (`OnInteractUsing`, `OnInsertBattery`, `OnRemoveBattery`, `OnMapInit`) to consistently call `Vehicle.RefreshCanRun(uid)` instead of duplicating `_actionBlocker.UpdateCanMove`, keeping the behavior centralized and aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs`:
- Around line 92-112: `_mechHands` can stay null when `HandsComponent` is added
after `LocalPilotedMechChanged`, so the mech hands UI never initializes in the
current session. Update `OnMechHandAdded` in `BkmVovaMechHandsUIController` to
recover from this late-start case by reloading hands when the added component
belongs to `_mechUid` and `_mechHands` is still null, or add a dedicated startup
hook for `HandsComponent` that calls `LoadMechHands`. Keep the existing
`AddHandButton` and `SetActiveHand` flow intact once hands are restored.
In `@Content.Server/Backmen/VovaMech/BkmVovaMechSystem.cs`:
- Around line 144-148: The OnEntry flow in BkmVovaMechSystem currently returns
silently when TryInsert(uid, args.User, component) fails, unlike the earlier
mind/CanOperate checks that show "mech-no-enter". Update the TryInsert failure
branch to send the same popup/feedback to the user before returning, keeping the
UX consistent in OnEntry and preserving the existing args.Handled behavior only
on success.
In `@Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs`:
- Line 483: The buckle release check in SharedBuckleSystem.Buckle should
preserve SoftCritical behavior, since _mobState.IsIncapacitated(buckle) only
covers hard crit/dead and drops soft crit. Update the condition around
HasComp<KnockedDownComponent> and the incapacitation check so it matches the
previous IsCritical || IsDead semantics, keeping SoftCritical entities lying
down when unbuckled.
In `@Content.Shared/Vehicle/VehicleSystem.cs`:
- Around line 113-189: In TrySetOperator, when removeExisting is true and uid
already has a VehicleOperatorComponent for another vehicle, clear that previous
vehicle’s operator state before reassigning. Update VehicleSystem.TrySetOperator
so it removes the old operator from the previous VehicleComponent, refreshes
that vehicle’s state (including operator/can-run related flags), and only then
assigns uid to the new entity. Use the existing operator handling paths around
_operatorQuery, VehicleOperatorComponent.Vehicle, and RefreshCanRun to keep both
vehicles consistent.
In `@Content.Shared/Vehicle/VehicleSystem.Operator.cs`:
- Around line 18-30: Добавьте проверку _timing.ApplyingState в обработчики
OnVehicleStrapped и OnVehicleUnstrapped, чтобы они вели себя согласованно с
OnContainerEntInserted/OnContainerEntRemoved и обработчиками в
VehicleSystem.Key.cs. Сейчас эти события сразу вызывают TrySetOperator, что
может повторно срабатывать при применении клиентского состояния; перед вызовом
TrySetOperator просто выходите из обработчика, если _timing.ApplyingState
истинно.
In `@Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml`:
- Around line 232-248: `MechHamtr` still relies on the default
`VehicleSystem.CanOperate` hand requirement, which blocks `MobHamster` from
piloting because it has no `HandsComponent`. Update the `MechHamtr` entity
definition in the mech YAML to explicitly set `requiresHands` to false on the
`Vehicle` component, keeping the existing `operatorWhitelist` for `Hamster`
intact so the mech can be operated without hands.
---
Nitpick comments:
In `@Content.Server/Mech/Systems/MechSystem.cs`:
- Line 314: Remove the redundant direct `_actionBlocker.UpdateCanMove` path in
`MechSystem`: `Vehicle.RefreshCanRun(uid)` already performs the move-state
update and appearance refresh, so the extra call after
`InsertBattery`/`RemoveBattery` should be dropped. Update the remaining direct
handlers in `MechSystem` (`OnInteractUsing`, `OnInsertBattery`,
`OnRemoveBattery`, `OnMapInit`) to consistently call
`Vehicle.RefreshCanRun(uid)` instead of duplicating
`_actionBlocker.UpdateCanMove`, keeping the behavior centralized and aligned.
In `@Content.Shared/Backmen/VovaMech/SharedBkmVovaMechSystem.cs`:
- Around line 154-167: The DoAfter startup logic for mech entry is duplicated
between OnDragDrop and StartEntryDoAfter, so move the shared setup into a common
helper in BkmVovaMechSystem (for example, a protected StartEntryDoAfter method
taking EntityUid uid, BkmPilotableMechComponent component, and the user/dragged
entity). Have both OnDragDrop and the server verb path call that helper so the
DoAfterArgs construction stays consistent, including flags like BreakOnMove and
the entry event type.
- Around line 17-21: Make the shared dependencies in SharedBkmVovaMechSystem
protected instead of private so the server-side BkmVovaMechSystem can reuse them
directly rather than reinjecting the same systems. Update the dependency fields
for _actionBlocker and _doAfter in SharedBkmVovaMechSystem, then remove the
duplicated ActionBlocker/DoAfter/_vehicle fields from BkmVovaMechSystem and
switch its usage to the inherited ActionBlocker, DoAfter, and Vehicle members.
In `@Content.Shared/Mech/EntitySystems/SharedMechSystem.cs`:
- Around line 67-69: The attack/weapon event subscriptions on
VehicleOperatorComponent are too broad for SharedMechSystem and should be split
by responsibility. Move the generic cancel logic for AttackAttemptEvent and
CanAttackFromContainerEvent into VehicleSystem, keep only the mech-specific
GetMeleeWeaponEvent fallback in SharedMechSystem, and preserve the existing
TryComp<MechComponent>/_pilotableMechQuery checks while relocating the handlers
OnGetMeleeWeapon, OnCanAttackFromContainer, and OnAttackAttempt to the
appropriate system.
In `@Content.Shared/Vehicle/VehicleSystem.Key.cs`:
- Around line 28-48: `OnGenericKeyedEntInserted` and `OnGenericKeyedEntRemoved`
in `VehicleSystem.Key` duplicate the same state checks, container ID guard,
vehicle lookup, and `RefreshCanRun` call; extract this shared flow into a
private helper and have both handlers delegate to it. Keep the helper centered
around the existing `Entity<GenericKeyedVehicleComponent>` and `RefreshCanRun`
path so the behavior remains identical while removing the repeated logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0da1a7a4-7410-483a-9a19-78c5502c2982
⛔ Files ignored due to path filters (5)
Resources/Textures/Objects/Vehicles/janicart.rsi/vehicle-moving.pngis excluded by!**/*.pngResources/Textures/Objects/Vehicles/janicart.rsi/vehicle.pngis excluded by!**/*.pngResources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair.pngis excluded by!**/*.pngResources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_folded.pngis excluded by!**/*.pngResources/Textures/Objects/Vehicles/wheelchair.rsi/wheelchair_overlay.pngis excluded by!**/*.png
📒 Files selected for processing (59)
Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.csContent.Client/Backmen/VovaMech/BkmVovaMechSystem.csContent.Client/Buckle/BuckleSystem.csContent.Client/CardboardBox/CardboardBoxSystem.csContent.Client/MainMenu/UI/MainMenuControl.xamlContent.Client/MainMenu/UI/MainMenuControl.xaml.csContent.Client/Options/UI/EscapeMenu.xamlContent.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.csContent.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xamlContent.Client/Weapons/Ranged/Systems/GunSystem.csContent.Server/Backmen/VovaMech/BkmVovaMechSystem.csContent.Server/CardboardBox/CardboardBoxSystem.csContent.Server/Mech/Equipment/EntitySystems/MechGrabberSystem.csContent.Server/Mech/Systems/MechEquipmentSystem.csContent.Server/Mech/Systems/MechSystem.csContent.Server/Physics/Controllers/MoverController.csContent.Server/Weapons/Ranged/Systems/GunSystem.csContent.Shared/ActionBlocker/ActionBlockerSystem.csContent.Shared/Backmen/VovaMech/BkmPilotableMechComponent.csContent.Shared/Backmen/VovaMech/GetGunHandsHolderEvent.csContent.Shared/Backmen/VovaMech/SharedBkmVovaMechSystem.csContent.Shared/Buckle/Components/StrapComponent.csContent.Shared/Buckle/SharedBuckleSystem.Buckle.csContent.Shared/CardboardBox/Components/CardboardBoxComponent.csContent.Shared/DoAfter/DoAfter.csContent.Shared/DoAfter/SharedDoAfterSystem.Update.csContent.Shared/DoAfter/SharedDoAfterSystem.csContent.Shared/Mech/Components/MechComponent.csContent.Shared/Mech/Components/MechPilotComponent.csContent.Shared/Mech/EntitySystems/SharedMechSystem.Relay.csContent.Shared/Mech/EntitySystems/SharedMechSystem.csContent.Shared/Movement/Components/InputMoverComponent.csContent.Shared/Movement/Events/UpdateCanMoveEvent.csContent.Shared/Movement/Systems/SharedMoverController.Input.csContent.Shared/Movement/Systems/SharedMoverController.Relay.csContent.Shared/Movement/Systems/SharedMoverController.csContent.Shared/Vehicle/Components/ContainerVehicleComponent.csContent.Shared/Vehicle/Components/GenericKeyedVehicleComponent.csContent.Shared/Vehicle/Components/StrapVehicleComponent.csContent.Shared/Vehicle/Components/VehicleComponent.csContent.Shared/Vehicle/Components/VehicleOperatorComponent.csContent.Shared/Vehicle/VehicleSystem.Key.csContent.Shared/Vehicle/VehicleSystem.Operator.csContent.Shared/Vehicle/VehicleSystem.csContent.Shared/Weapons/Ranged/Systems/SharedGunSystem.csResources/Locale/en-US/_backmen/vova-mech/vova-mech.ftlResources/Locale/en-US/escape-menu/ui/escape-menu.ftlResources/Locale/en-US/vehicle/vehicle.ftlResources/Locale/ru-RU/_backmen/vova-mech/vova-mech.ftlResources/Locale/ru-RU/escape-menu/ui/escape-menu.ftlResources/Prototypes/Entities/Objects/Specific/Mech/mechs.ymlResources/Prototypes/Entities/Objects/Vehicles/keys.ymlResources/Prototypes/Entities/Objects/Vehicles/vehicles.ymlResources/Prototypes/Entities/Structures/Storage/Closets/big_boxes.ymlResources/Prototypes/_Backmen/Entities/Mobs/NPCs/mechi_silicon.ymlResources/Prototypes/tags.ymlResources/Textures/Objects/Vehicles/janicart.rsi/meta.jsonResources/Textures/Objects/Vehicles/wheelchair.rsi/meta.jsonResources/migration.yml
💤 Files with no reviewable changes (5)
- Content.Shared/Mech/Components/MechPilotComponent.cs
- Content.Client/MainMenu/UI/MainMenuControl.xaml.cs
- Content.Shared/Movement/Systems/SharedMoverController.cs
- Content.Client/MainMenu/UI/MainMenuControl.xaml
- Content.Shared/Mech/Components/MechComponent.cs
| if (!_entities.TryGetComponent(mech, out HandsComponent? hands)) | ||
| { | ||
| _mechUid = mech; | ||
| _mechHands = null; | ||
| return; | ||
| } | ||
|
|
||
| LoadMechHands(mech.Value, hands); | ||
| } | ||
|
|
||
| private void OnMechHandAdded(Entity<HandsComponent> entity, string name, HandLocation location) | ||
| { | ||
| if (entity.Owner != _mechUid || _mechHands == null) | ||
| return; | ||
|
|
||
| if (!_handsSystem.TryGetHand((entity.Owner, entity.Comp), name, out var hand)) | ||
| return; | ||
|
|
||
| AddHandButton(name, hand.Value); | ||
| SetActiveHand(_mechHands.ActiveHandId); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n --type=cs -C5 'OnPlayerAddHand' Content.Client/Hands Content.Shared/HandsRepository: Rxup/space-station-14
Length of output: 2542
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE='Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs'
wc -l "$FILE"
cat -n "$FILE" | sed -n '1,220p'
printf '\n--- subscriptions/usages ---\n'
rg -n --type=cs -C4 'OnPlayerAddHand|OnPlayerRemoveHand|OnMechHandAdded|OnMechHandRemoved|LoadMechHands|HandsComponent' Content.Client/Backmen/VovaMech Content.Client Content.Shared/HandsRepository: Rxup/space-station-14
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --type=cs -C4 'event .*LocalPilotedMechChanged|LocalPilotedMechChanged\+|LocalPilotedMechChanged -|LocalPilotedMechChanged' Content.Client/Backmen/VovaMech Content.Shared Content.Client
printf '\n--- client hands startup section ---\n'
cat -n Content.Client/Hands/Systems/HandsSystem.cs | sed -n '58,110p'
printf '\n--- add hand method section ---\n'
cat -n Content.Shared/Hands/EntitySystems/SharedHandsSystem.cs | sed -n '70,115p'Repository: Rxup/space-station-14
Length of output: 11926
_mechHands не подхватывается при поздней инициализации рук меха
Если HandsComponent появляется после LocalPilotedMechChanged, _mechHands остаётся null до пересадки: OnMechHandAdded сразу выходит по этому флагу, а других путей повторно вызвать LoadMechHands нет. Добавь восстановление в OnMechHandAdded (или отдельный хук на старт HandsComponent), иначе UI рук меха может так и не появиться в текущей сессии пилотирования.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Content.Client/Backmen/VovaMech/BkmVovaMechHandsUIController.cs` around lines
92 - 112, `_mechHands` can stay null when `HandsComponent` is added after
`LocalPilotedMechChanged`, so the mech hands UI never initializes in the current
session. Update `OnMechHandAdded` in `BkmVovaMechHandsUIController` to recover
from this late-start case by reloading hands when the added component belongs to
`_mechUid` and `_mechHands` is still null, or add a dedicated startup hook for
`HandsComponent` that calls `LoadMechHands`. Keep the existing `AddHandButton`
and `SetActiveHand` flow intact once hands are restored.
| if (!TryInsert(uid, args.User, component)) | ||
| return; | ||
|
|
||
| args.Handled = true; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Нет обратной связи пользователю при неудачной вставке в OnEntry.
Если TryInsert вернёт false (например, слот уже занят другим оператором к моменту завершения DoAfter), метод просто возвращает управление без popup, в отличие от веток mind/CanOperate выше, где пользователь получает "mech-no-enter". Стоит показать аналогичное сообщение и здесь для консистентности UX.
🩹 Предлагаемый фикс
if (!TryInsert(uid, args.User, component))
+ {
+ _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User);
return;
+ }
args.Handled = true;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!TryInsert(uid, args.User, component)) | |
| return; | |
| args.Handled = true; | |
| } | |
| if (!TryInsert(uid, args.User, component)) | |
| { | |
| _popup.PopupEntity(Loc.GetString("mech-no-enter", ("item", uid)), args.User); | |
| return; | |
| } | |
| args.Handled = true; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Content.Server/Backmen/VovaMech/BkmVovaMechSystem.cs` around lines 144 - 148,
The OnEntry flow in BkmVovaMechSystem currently returns silently when
TryInsert(uid, args.User, component) fails, unlike the earlier mind/CanOperate
checks that show "mech-no-enter". Update the TryInsert failure branch to send
the same popup/feedback to the user before returning, keeping the UX consistent
in OnEntry and preserving the existing args.Handled behavior only on success.
| Appearance.SetData(buckle, BuckleVisuals.Buckled, false); | ||
|
|
||
| if (HasComp<KnockedDownComponent>(buckle) || _mobState.IsCritical(buckle) || _mobState.IsDead(buckle)) | ||
| if (HasComp<KnockedDownComponent>(buckle) || _mobState.IsIncapacitated(buckle)) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files and inspect the exact implementations/comments.
git ls-files 'Content.Shared/**/MobStateSystem.cs' 'Content.Shared/**/SharedBuckleSystem.Buckle.cs' | sed -n '1,20p'
echo '--- outline MobStateSystem ---'
ast-grep outline Content.Shared/Mobs/Systems/MobStateSystem.cs --view expanded || true
echo '--- outline SharedBuckleSystem.Buckle ---'
ast-grep outline Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs --view expanded || true
echo '--- relevant lines in MobStateSystem ---'
rg -n "IsCritical|IsIncapacitated|SoftCritical|Critical|Dead" Content.Shared/Mobs/Systems/MobStateSystem.cs
echo '--- relevant lines in SharedBuckleSystem.Buckle.cs ---'
rg -n "IsIncapacitated|IsCritical|IsDead|KnockedDownComponent|Stand" Content.Shared/Buckle/SharedBuckleSystem.Buckle.csRepository: Rxup/space-station-14
Length of output: 2229
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the relevant implementation snippets with line numbers.
sed -n '1,260p' Content.Shared/Mobs/Systems/MobStateSystem.cs | cat -n | sed -n '1,260p'
echo '---'
sed -n '440,520p' Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs | cat -n | sed -n '1,120p'Repository: Rxup/space-station-14
Length of output: 9658
IsIncapacitated теряет SoftCritical.
Она охватывает только hard crit/dead, а прежняя проверка IsCritical || IsDead включала и SoftCritical. Из-за этого при отвязке soft-crit сущности будут вставать вместо того, чтобы оставаться лежащими.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Content.Shared/Buckle/SharedBuckleSystem.Buckle.cs` at line 483, The buckle
release check in SharedBuckleSystem.Buckle should preserve SoftCritical
behavior, since _mobState.IsIncapacitated(buckle) only covers hard crit/dead and
drops soft crit. Update the condition around HasComp<KnockedDownComponent> and
the incapacitation check so it matches the previous IsCritical || IsDead
semantics, keeping SoftCritical entities lying down when unbuckled.
| public bool TrySetOperator(Entity<VehicleComponent> entity, EntityUid? uid, bool removeExisting = true) | ||
| { | ||
| // Early exit if no change needed | ||
| if (entity.Comp.Operator == null && uid is null) | ||
| return false; | ||
|
|
||
| // Early exit if setting the same operator that's already present | ||
| if (entity.Comp.Operator == uid) | ||
| return true; | ||
|
|
||
| // Do not run logic if the entity is already operating a vehicle. | ||
| // However, if they are operating *this* vehicle, return true (they are indeed the operator) | ||
| if (uid is not null && _operatorQuery.TryComp(uid, out var eOperator)) | ||
| { | ||
| if (eOperator.Vehicle == entity.Owner) | ||
| return true; | ||
|
|
||
| // If trying to operate another vehicle, fail unless removeExisting is true | ||
| if (!removeExisting) | ||
| return false; | ||
| } | ||
|
|
||
| if (!removeExisting && entity.Comp.Operator is not null) | ||
| return false; | ||
|
|
||
| if (uid != null && !CanOperate(entity.AsNullable(), uid.Value)) | ||
| return false; | ||
|
|
||
| var oldOperator = entity.Comp.Operator; | ||
|
|
||
| if (oldOperator is { } currentOperator && | ||
| _operatorQuery.TryComp(currentOperator, out var currentOperatorComponent)) | ||
| { | ||
| var exitEvent = new OnVehicleExitedEvent(entity, currentOperator); | ||
| RaiseLocalEvent(currentOperator, ref exitEvent); | ||
|
|
||
| currentOperatorComponent.Vehicle = null; | ||
| RemCompDeferred<VehicleOperatorComponent>(currentOperator); | ||
| RemCompDeferred<RelayInputMoverComponent>(currentOperator); | ||
| RemCompDeferred<InteractionRelayComponent>(currentOperator); | ||
| } | ||
|
|
||
| entity.Comp.Operator = uid; | ||
|
|
||
| if (uid != null) | ||
| { | ||
| if (_operatorQuery.HasComp(uid.Value)) | ||
| { | ||
| var vehicleOperator = Comp<VehicleOperatorComponent>(uid.Value); | ||
| vehicleOperator.Vehicle = entity.Owner; | ||
| Dirty(uid.Value, vehicleOperator); | ||
| } | ||
| else | ||
| { | ||
| var vehicleOperator = AddComp<VehicleOperatorComponent>(uid.Value); | ||
| vehicleOperator.Vehicle = entity.Owner; | ||
| Dirty(uid.Value, vehicleOperator); | ||
| } | ||
|
|
||
| _mover.SetRelay(uid.Value, entity); | ||
|
|
||
| var enterEvent = new OnVehicleEnteredEvent(entity, uid.Value); | ||
| RaiseLocalEvent(uid.Value, ref enterEvent); | ||
| } | ||
| else | ||
| { | ||
| RemCompDeferred<MovementRelayTargetComponent>(entity); | ||
| } | ||
|
|
||
| RefreshCanRun((entity, entity.Comp)); | ||
|
|
||
| var setEvent = new VehicleOperatorSetEvent(uid, oldOperator); | ||
| RaiseLocalEvent(entity, ref setEvent); | ||
|
|
||
| Dirty(entity); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== VehicleSystem outline ==\n'
ast-grep outline Content.Shared/Vehicle/VehicleSystem.cs --view expanded || true
printf '\n== TrySetOperator / TryRemoveOperator / operator-related symbols ==\n'
rg -n "TrySetOperator|TryRemoveOperator|VehicleOperatorSetEvent|OnVehicleExitedEvent|OnVehicleEnteredEvent|MovementRelayTargetComponent|RefreshCanRun|UpdateAppearance|HasOperator|CanRun" Content.Shared Content.Server Content.Client -S
printf '\n== Call sites that pass removeExisting: true or use TrySetOperator ==\n'
rg -n "TrySetOperator\s*\(" -S .Repository: Rxup/space-station-14
Length of output: 42592
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in \
Content.Shared/Vehicle/VehicleSystem.cs \
Content.Shared/Vehicle/VehicleSystem.Operator.cs \
Content.Shared/Mech/EntitySystems/SharedMechSystem.cs \
Content.Server/Backmen/VovaMech/BkmVovaMechSystem.cs \
Content.Shared/Vehicle/Components/VehicleComponent.cs
do
echo "== $f =="
wc -l "$f"
done
echo
echo "== VehicleSystem.cs relevant slice =="
sed -n '1,340p' Content.Shared/Vehicle/VehicleSystem.cs | cat -n | sed -n '1,340p'
echo
echo "== VehicleSystem.Operator.cs =="
sed -n '1,220p' Content.Shared/Vehicle/VehicleSystem.Operator.cs | cat -n
echo
echo "== SharedMechSystem.cs relevant slice =="
sed -n '1,520p' Content.Shared/Mech/EntitySystems/SharedMechSystem.cs | cat -n | sed -n '1,520p'
echo
echo "== BkmVovaMechSystem.cs relevant slice =="
sed -n '1,240p' Content.Server/Backmen/VovaMech/BkmVovaMechSystem.cs | cat -n | sed -n '1,240p'
echo
echo "== VehicleComponent.cs =="
sed -n '1,160p' Content.Shared/Vehicle/Components/VehicleComponent.cs | cat -nRepository: Rxup/space-station-14
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path('Content.Shared/Vehicle/VehicleSystem.cs')
text = path.read_text()
for needle in ['public bool TrySetOperator', 'public bool TryRemoveOperator', 'public bool HasOperator', 'public void RefreshCanRun', 'private void UpdateAppearance']:
idx = text.find(needle)
print(f"\n=== {needle} @ {idx} ===")
if idx != -1:
start = max(0, text.rfind('\n', 0, idx - 200))
end = min(len(text), idx + 1400)
snippet = text[start:end]
for i, line in enumerate(snippet.splitlines(), 1):
print(f"{i:>4}: {line}")
PYRepository: Rxup/space-station-14
Length of output: 8191
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== VehicleOperatorComponent =='
sed -n '1,200p' Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs | cat -n
echo
echo '== References to VehicleOperatorShutdownEvent / VehicleOperatorSetEvent handlers =='
rg -n "VehicleOperatorShutdownEvent|VehicleOperatorSetEvent" Content.Shared Content.Server Content.Client -S
echo
echo '== References to VehicleOperatorComponent.Vehicle assignments or reads =='
rg -n "\.Vehicle\b" Content.Shared Content.Server Content.Client -S | rg "VehicleOperatorComponent|VehicleSystem|OnOperatorSet|TrySetOperator|TryRemoveOperator|VehicleOperatorShutdownEvent|VehicleOperatorSetEvent" -n -SRepository: Rxup/space-station-14
Length of output: 4379
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path('Content.Shared/Vehicle/Components/VehicleOperatorComponent.cs')
print(path.read_text())
PYRepository: Rxup/space-station-14
Length of output: 753
При removeExisting == true нужно очищать старый транспорт оператора. Если uid уже был оператором другого VehicleComponent, текущий код перепривязывает VehicleOperatorComponent.Vehicle, но не снимает его со старого транспорта, и там остаётся устаревший Operator/HasOperator/CanRun.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Content.Shared/Vehicle/VehicleSystem.cs` around lines 113 - 189, In
TrySetOperator, when removeExisting is true and uid already has a
VehicleOperatorComponent for another vehicle, clear that previous vehicle’s
operator state before reassigning. Update VehicleSystem.TrySetOperator so it
removes the old operator from the previous VehicleComponent, refreshes that
vehicle’s state (including operator/can-run related flags), and only then
assigns uid to the new entity. Use the existing operator handling paths around
_operatorQuery, VehicleOperatorComponent.Vehicle, and RefreshCanRun to keep both
vehicles consistent.
| private void OnVehicleStrapped(Entity<StrapVehicleComponent> ent, ref StrappedEvent args) | ||
| { | ||
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | ||
| return; | ||
| TrySetOperator((ent, vehicle), args.Buckle); | ||
| } | ||
|
|
||
| private void OnVehicleUnstrapped(Entity<StrapVehicleComponent> ent, ref UnstrappedEvent args) | ||
| { | ||
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | ||
| return; | ||
| TrySetOperator((ent, vehicle), null); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Отсутствует проверка _timing.ApplyingState в отличие от остальных обработчиков.
OnVehicleStrapped/OnVehicleUnstrapped не проверяют _timing.ApplyingState, тогда как аналогичные обработчики в этом же файле (OnContainerEntInserted/OnContainerEntRemoved, Line 34, 45) и в VehicleSystem.Key.cs (OnGenericKeyedInsertAttempt, OnGenericKeyedEntInserted, OnGenericKeyedEntRemoved) явно игнорируют логику во время применения состояния клиентом. Это несогласованность паттерна: при применении сетевого состояния на клиенте TrySetOperator может выполниться повторно (например, повторно поднять OnVehicleEnteredEvent/OnVehicleExitedEvent, снова вызвать _mover.SetRelay), что может приводить к лишним побочным эффектам или рассинхронизации предсказания.
Прошу подтвердить, действительно ли StrappedEvent/UnstrappedEvent не поднимаются во время применения состояния (в отличие от контейнерных событий), иначе стоит добавить такую же защиту.
♻️ Возможный фикс для согласованности
private void OnVehicleStrapped(Entity<StrapVehicleComponent> ent, ref StrappedEvent args)
{
+ if (_timing.ApplyingState)
+ return;
+
if (!_vehicleQuery.TryComp(ent, out var vehicle))
return;
TrySetOperator((ent, vehicle), args.Buckle);
}
private void OnVehicleUnstrapped(Entity<StrapVehicleComponent> ent, ref UnstrappedEvent args)
{
+ if (_timing.ApplyingState)
+ return;
+
if (!_vehicleQuery.TryComp(ent, out var vehicle))
return;
TrySetOperator((ent, vehicle), null);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void OnVehicleStrapped(Entity<StrapVehicleComponent> ent, ref StrappedEvent args) | |
| { | |
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | |
| return; | |
| TrySetOperator((ent, vehicle), args.Buckle); | |
| } | |
| private void OnVehicleUnstrapped(Entity<StrapVehicleComponent> ent, ref UnstrappedEvent args) | |
| { | |
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | |
| return; | |
| TrySetOperator((ent, vehicle), null); | |
| } | |
| private void OnVehicleStrapped(Entity<StrapVehicleComponent> ent, ref StrappedEvent args) | |
| { | |
| if (_timing.ApplyingState) | |
| return; | |
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | |
| return; | |
| TrySetOperator((ent, vehicle), args.Buckle); | |
| } | |
| private void OnVehicleUnstrapped(Entity<StrapVehicleComponent> ent, ref UnstrappedEvent args) | |
| { | |
| if (_timing.ApplyingState) | |
| return; | |
| if (!_vehicleQuery.TryComp(ent, out var vehicle)) | |
| return; | |
| TrySetOperator((ent, vehicle), null); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Content.Shared/Vehicle/VehicleSystem.Operator.cs` around lines 18 - 30,
Добавьте проверку _timing.ApplyingState в обработчики OnVehicleStrapped и
OnVehicleUnstrapped, чтобы они вели себя согласованно с
OnContainerEntInserted/OnContainerEntRemoved и обработчиками в
VehicleSystem.Key.cs. Сейчас эти события сразу вызывают TrySetOperator, что
может повторно срабатывать при применении клиентского состояния; перед вызовом
TrySetOperator просто выходите из обработчика, если _timing.ApplyingState
истинно.
| maxEquipmentAmount: 2 | ||
| airtight: true | ||
| equipmentWhitelist: | ||
| tags: | ||
| - SmallMech | ||
| pilotWhitelist: | ||
| - type: Vehicle | ||
| transferDamageModifier: | ||
| coefficients: | ||
| Blunt: 0.2 | ||
| Slash: 0.2 | ||
| Piercing: 0.2 | ||
| Cold: 0.2 | ||
| Heat: 0.2 | ||
| Shock: 0.2 | ||
| operatorWhitelist: | ||
| tags: | ||
| - Hamster |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Ищем мобов с тегами Hamster / VimPilot и проверяем наличие компонента Hands
rg -n -B3 -A15 'tags:\s*\n\s*-\s*Hamster' -g '*.yml' -P
rg -n -B3 -A15 'tags:\s*\n\s*-\s*VimPilot' -g '*.yml' -PRepository: Rxup/space-station-14
Length of output: 159
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== mechs.yml relevant sections =="
sed -n '220,260p' Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml
echo
sed -n '300,330p' Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml
echo
echo "== requiresHands in mechs.yml =="
rg -n 'requiresHands|operatorWhitelist|MechHamtr|MechVim' Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml
echo
echo "== HandsComponent / hand-related presence for likely operators =="
rg -n 'Hamster|VimPilot|HandsComponent|hands component|Hands' Resources/Prototypes -g '*.yml'Repository: Rxup/space-station-14
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== tags.yml references =="
rg -n -A4 -B4 'Hamster|VimPilot' Resources/Prototypes -g '*.yml'
echo
echo "== component definitions with hands =="
rg -n 'HandsComponent|hands' Content.* Resources -g '*.yml' -g '*.cs' | head -n 200Repository: Rxup/space-station-14
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== MobHamster definition =="
sed -n '3438,3495p' Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
echo
echo "== mouse / VimPilot-related definition =="
sed -n '1,120p' Resources/Prototypes/_Backmen/Entities/Mobs/Species/mouse.yml
echo
echo "== other VimPilot mobs with Hands component nearby =="
rg -n -A6 -B6 '^\s*- type: Hands\b|^\s*- type: Tag\b|VimPilot|Hamster\b' Resources/Prototypes/Entities/Mobs/NPCs/pets.yml Resources/Prototypes/Entities/Mobs/NPCs/animals.yml Resources/Prototypes/_Backmen/Entities/Mobs/Species/mouse.yml -g '*.yml'Repository: Rxup/space-station-14
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== VehicleComponent and VehicleSystem references to requiresHands =="
rg -n -A4 -B4 'requiresHands|CanOperate|HandsComponent' Content.* Resources -g '*.cs' -g '*.yml' | head -n 120Repository: Rxup/space-station-14
Length of output: 10512
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path("Resources/Prototypes/Entities/Mobs/NPCs/animals.yml")
lines = path.read_text().splitlines()
def extract_entity_block(entity_id):
start = None
for i, line in enumerate(lines):
if line.strip() == f"id: {entity_id}":
start = i
break
if start is None:
return None
end = len(lines)
for j in range(start + 1, len(lines)):
if lines[j].startswith("- type: entity"):
end = j
break
return start, end, lines[start:end]
for entity_id in ["MobHamster", "MobHamsterHamlet"]:
res = extract_entity_block(entity_id)
if not res:
print(f"{entity_id}: NOT FOUND")
continue
start, end, block = res
has_hands = any(l.strip() == "- type: Hands" for l in block)
tags = []
for idx, l in enumerate(block):
if l.strip() == "- type: Tag":
for k in range(idx + 1, min(idx + 12, len(block))):
if block[k].strip() == "tags:":
for m in range(k + 1, min(k + 10, len(block))):
s = block[m].strip()
if s.startswith("- "):
tags.append(s[2:])
break
print(f"{entity_id}: lines {start+1}-{end}")
print(f" HandsComponent: {has_hands}")
print(f" Tags: {tags}")
print()
# Check operator whitelist targets in mechs.yml
mechs = Path("Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml").read_text().splitlines()
for mech_id in ["MechHamtr", "MechVim"]:
start = None
for i, line in enumerate(mechs):
if line.strip() == f"id: {mech_id}":
start = i
break
if start is None:
print(f"{mech_id}: NOT FOUND")
continue
end = len(mechs)
for j in range(start + 1, len(mechs)):
if mechs[j].startswith("- type: entity"):
end = j
break
block = mechs[start:end]
req = [l.strip() for l in block if "requiresHands" in l]
wl = []
capture = False
for l in block:
if l.strip().startswith("operatorWhitelist:"):
capture = True
continue
if capture and l.startswith(" - type:"):
break
if capture and l.strip().startswith("- "):
wl.append(l.strip()[2:])
print(f"{mech_id}: lines {start+1}-{end}")
print(f" requiresHands lines: {req if req else 'none (default applies)'}")
print(f" operatorWhitelist tags: {wl}")
print()
PYRepository: Rxup/space-station-14
Length of output: 541
Явно отключить requiresHands у MechHamtr — MobHamster не имеет HandsComponent, а VehicleSystem.CanOperate по умолчанию требует руки, так что хомяки не смогут пилотировать этот мех.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml` around lines
232 - 248, `MechHamtr` still relies on the default `VehicleSystem.CanOperate`
hand requirement, which blocks `MobHamster` from piloting because it has no
`HandsComponent`. Update the `MechHamtr` entity definition in the mech YAML to
explicitly set `requiresHands` to false on the `Vehicle` component, keeping the
existing `operatorWhitelist` for `Hamster` intact so the mech can be operated
without hands.




Summary by CodeRabbit
New Features
Bug Fixes